﻿Imports System.Diagnostics.Contracts
Imports AutoMapper
Imports CookComputing.XmlRpc
Imports FTTK.Entities
Imports FTTK.Services.EventArgs
Imports FTTK.Exceptions
Imports FTTK.XmlRpc.Interfaces
Imports FTTK.Services.Interfaces

Namespace Services.Implementations.XmlRpc
    ''' <summary>
    ''' 
    ''' </summary>
    ''' <remarks></remarks>
    Public MustInherit Class BaseService
        Implements IBaseService

        Shared Sub New()
            BaseServiceMapping.DefineMapping()
        End Sub


        ''' <summary>
        ''' Initializes a new instance of the <see cref="BaseService" /> class.
        ''' </summary>
        ''' <remarks></remarks>
        Public Sub New()
            Me.SearchProxy = CreateProxy(Of ISearch)()
        End Sub

        Public Sub New(cacheAdapter As ICacheAdapter)
            Me.CacheAdapter = cacheAdapter
            Me.SearchProxy = CreateProxy(Of ISearch)()
        End Sub

        Public Sub New(apiKey As String, cacheAdapter As ICacheAdapter)
            Me.ApiKey = apiKey
            Me.SearchProxy = CreateProxy(Of ISearch)()
            Me.CacheAdapter = cacheAdapter
        End Sub


        ''' <summary>
        ''' Initializes a new instance of the <see cref="BaseService" /> class.
        ''' </summary>
        ''' <param name="apiKey">The API key.</param>
        ''' <remarks></remarks>
        Public Sub New(apiKey As String)
            Me.ApiKey = apiKey
            Me.SearchProxy = CreateProxy(Of ISearch)()
        End Sub


        Private _CacheAdapter As ICacheAdapter

        ''' <summary>
        ''' Gets or sets the cache adapter.
        ''' </summary>
        ''' <value>The cache adapter.</value>
        ''' <remarks></remarks>
        Public Property CacheAdapter As ICacheAdapter
            Get
                Return Me._CacheAdapter
            End Get
            Set(ByVal value As ICacheAdapter)
                Contract.Requires(Of ArgumentNullException)(value IsNot Nothing, "CacheAdapter must be not null!")
                Me._CacheAdapter = value
            End Set
        End Property


        Protected Authentication As FTTK.XmlRpc.Authentication

        Private _ApiKey As String

        ''' <summary>
        ''' Gets or sets the API key.
        ''' </summary>
        ''' <value>The API key.</value>
        ''' <remarks></remarks>
        Public Property ApiKey As String
            Get
                Return If(Authentication Is Nothing, Nothing, Authentication.ApiKey)
            End Get
            Set(ByVal value As String)
                Contract.Requires(Of ArgumentNullException)(Not String.IsNullOrWhiteSpace(value), "ApiKey must be not null!")
                If value <> ApiKey Then
                    Authentication = New FTTK.XmlRpc.Authentication() With {.ApiKey = value}
                End If
            End Set
        End Property

        Protected Property SearchProxy As ISearch

        Protected Function CreateProxy(Of T)() As T
            Dim proxy = XmlRpcProxyGen.Create(Of T)()
            Return CTypeDynamic(Of T)(proxy)
        End Function

        Protected Sub ManageException(ByVal ex As Exception)
            If TypeOf ex Is XmlRpcFaultException Then
                Throw CreateException(CType(ex, XmlRpcFaultException))
            Else
                Throw ex
            End If
        End Sub

        ''' <summary>
        ''' Creates the exception.
        ''' </summary>
        ''' <param name="rpcException">RPC exception from which creates the Fotolia Exception.</param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Protected Function CreateException(rpcException As XmlRpcFaultException) As FotoliaException
            Contract.Requires(Of ArgumentNullException)(rpcException IsNot Nothing, "rpcException must be not null!")
            Return Mapper.Map(Of XmlRpcFaultException, FotoliaException)(rpcException)
        End Function
#Region "Test method"

        Public Overridable Function Test() As Boolean Implements IBaseService.Test
            Dim retVal As Boolean = False
            Try
                Dim result = SearchProxy.Test(Me.Authentication)
                If result.ContainsKey("test") Then
                    retVal = (String.Compare(result.Item("test").ToString(), "SUCCESS", True) = 0)
                End If
            Catch ex As Exception
                ManageException(ex)
            End Try
            Return retVal
        End Function

        Public Overridable Sub TestAsync(userState As Object) Implements IBaseService.TestAsync
            Try
                Dim result = SearchProxy.BeginTest(Me.Authentication, AddressOf TestCallBack, userState)
            Catch ex As Exception
                If TypeOf ex Is XmlRpcFaultException Then
                    Dim xmlrpcEx = CType(ex, XmlRpcFaultException)
                    OnTestCompleted(New TestCompletedEventArgs() With {.Error = New [Error] With {.Code = xmlrpcEx.FaultCode.ToString(),
                                                                                                      .Message = xmlrpcEx.Message},
                                                                           .Result = False,
                                                                           .UserState = userState})
                Else
                    Throw
                End If
            End Try
        End Sub

        Protected Sub TestCallBack(asr As IAsyncResult)
            Dim clientResult = CType(asr, XmlRpcAsyncResult)
            Dim proxy = CType(clientResult.ClientProtocol, ISearch)
            Try
                Dim asyncResult = proxy.EndTest(asr)
                Dim retval = False
                If asyncResult.ContainsKey("test") Then
                    retval = (String.Compare(asyncResult.Item("test").ToString(), "SUCCESS", True) = 0)
                End If
                OnTestCompleted(New TestCompletedEventArgs() With {.Error = Nothing,
                                                                       .Result = retval,
                                                                       .UserState = clientResult.AsyncState})
            Catch ex As Exception
                If TypeOf ex Is XmlRpcFaultException Then
                    Dim xmlrpcEx = CType(ex, XmlRpcFaultException)
                    OnTestCompleted(New TestCompletedEventArgs() With {.Error = New [Error] With {.Code = xmlrpcEx.FaultCode.ToString(),
                                                                                                      .Message = xmlrpcEx.Message},
                                                                           .Result = False})
                Else
                    Throw
                End If
            End Try
        End Sub

        Public Event TestCompleted(ByVal sender As Object, ByVal e As EventArgs.TestCompletedEventArgs) Implements IBaseService.TestCompleted

        Protected Sub OnTestCompleted(ByVal e As EventArgs.TestCompletedEventArgs)
            RaiseEvent TestCompleted(Me, e)
        End Sub

#End Region

#Region "GetData method"

        Public Overridable Function GetData() As FotoliaData Implements IBaseService.GetData
            Dim retVal As FotoliaData = Nothing
            Try
                retVal = ExecuteQuery(Of FotoliaData)("GetData",
                                                    Function() SearchProxy.GetData(Me.Authentication),
                                                    Me.Authentication)
            Catch ex As Exception
                ManageException(ex)
            End Try
            Return retVal
        End Function

        Public Overridable Sub GetDataAsync(userState As Object) Implements IBaseService.GetDataAsync
            Try
                Dim result = GetResultFromCache(Of FotoliaData)("GetData", Me.Authentication)
                If result Is Nothing Then
                    SearchProxy.BeginGetData(Me.Authentication, AddressOf GetDataCallBack, userState)
                Else
                    OnGetDataCompleted(New GetDataCompletedEventArgs() With {.Error = Nothing,
                                                                             .Result = True,
                                                                             .FotoliaData = result,
                                                                             .UserState = userState})
                End If
            Catch ex As Exception
                If TypeOf ex Is XmlRpcFaultException Then
                    Dim xmlrpcEx = CType(ex, XmlRpcFaultException)
                    OnGetDataCompleted(New GetDataCompletedEventArgs() With {.Error = Mapper.Map(Of XmlRpcFaultException, [Error])(xmlrpcEx),
                                                                             .Result = False,
                                                                             .FotoliaData = Nothing,
                                                                             .UserState = userState})
                Else
                    Throw
                End If
            End Try
        End Sub

        Private Sub GetDataCallBack(asr As IAsyncResult)
            Dim clientResult = CType(asr, XmlRpcAsyncResult)
            Dim proxy = CType(clientResult.ClientProtocol, ISearch)
            Try
                Dim asyncResult = CType(proxy.EndTest(asr), XmlRpcStruct)
                Dim result = Mapper.Map(Of XmlRpcStruct, Entities.FotoliaData)(asyncResult)
                SetResultInCache(Of FotoliaData)("GetData", result, Me.Authentication)
                OnGetDataCompleted(New GetDataCompletedEventArgs() With {.Error = Nothing,
                                                                         .Result = True,
                                                                         .FotoliaData = result,
                                                                         .UserState = clientResult.AsyncState})
            Catch ex As Exception
                If TypeOf ex Is XmlRpcFaultException Then
                    Dim xmlrpcEx = CType(ex, XmlRpcFaultException)
                    OnGetDataCompleted(New GetDataCompletedEventArgs() With {.Error = Mapper.Map(Of XmlRpcFaultException, [Error])(xmlrpcEx),
                                                                            .Result = False,
                                                                            .UserState = clientResult.AsyncState})
                Else
                    Throw
                End If
            End Try
        End Sub

        Public Event GetDataCompleted(ByVal sender As Object, ByVal e As EventArgs.GetDataCompletedEventArgs) Implements IBaseService.GetDataCompleted

        Protected Sub OnGetDataCompleted(ByVal e As EventArgs.GetDataCompletedEventArgs)
            RaiseEvent GetDataCompleted(Me, e)
        End Sub
#End Region

#Region "Cache manager"
        ''' <summary>
        ''' Retrieves the result from cache.
        ''' </summary>
        ''' <typeparam name="T"></typeparam>	
        ''' <param name="operationName">Name of the operation.</param>
        ''' <param name="args">The args of the operation.</param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Protected Function GetResultFromCache(Of T)(operationName As String, ParamArray args() As Object) As T
            Dim retval As T = Nothing
            If CacheAdapter IsNot Nothing Then
                Try
                    Dim container = RequestContainer.Create(operationName, args)
                    Dim hashKey = FTTK.Utilities.HashGenerator.GenerateKey(container)
                    retval = CacheAdapter.GetData(Of T)(hashKey)
                Catch ex As Exception
                    retval = Nothing
                End Try
            End If
            Return retval
        End Function

        ''' <summary>
        ''' Sets the result in cache.
        ''' </summary>
        ''' <typeparam name="T"></typeparam>	
        ''' <param name="operationName">Name of the operation.</param>
        ''' <param name="resultValue">The result value to store in cache.</param>
        ''' <param name="args">The args of the operation.</param>
        ''' <remarks></remarks>
        Protected Sub SetResultInCache(Of T)(operationName As String, resultValue As T, ParamArray args() As Object)
            If CacheAdapter IsNot Nothing Then
                Try
                    Dim container = RequestContainer.Create(operationName, args)
                    Dim hashKey = FTTK.Utilities.HashGenerator.GenerateKey(container)
                    CacheAdapter.SetData(Of T)(hashKey, resultValue)
                Catch ex As Exception
                End Try
            End If
        End Sub
#End Region

        Protected Function ExecuteQuery(Of Tout)(operationName As String, noCacheFunc As Func(Of XmlRpcStruct), ParamArray args() As Object) As Tout
            Dim retVal = GetResultFromCache(Of Tout)(operationName, args)
            If retVal Is Nothing Then
                Dim result = noCacheFunc()
                retVal = Mapper.Map(Of XmlRpcStruct, Tout)(result)
                SetResultInCache(Of Tout)(operationName, retVal, args)
            End If
            Return retVal
        End Function

        Protected Function ExecuteQuery(Of Tout)(operationName As String, noCacheFunc As Func(Of XmlRpcStruct()), ParamArray args() As Object) As Tout
            Dim retVal = GetResultFromCache(Of Tout)(operationName, args)
            If retVal Is Nothing Then
                Dim result = noCacheFunc()
                retVal = Mapper.Map(Of XmlRpcStruct(), Tout)(result)
                SetResultInCache(Of Tout)(operationName, retVal, args)
            End If
            Return retVal
        End Function
    End Class

End Namespace